/*
 * Copyright (c) 2010-2013 Ruben Chakhmakhchyan. All rights reserved.
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Eclipse Public License
 * Version 1.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://opensource.org/licenses/EPL-1.0 and read it before using this file.
 */

#include <assert.h>

#include "twain_model_impl.h"
#include "result_codes.h"
#include "strings.h"
#include "convert.h"

// TWAIN states
#define TW_STATE_PRESESSION                     1 // Source Manager not loaded
#define TW_STATE_SOURCEMANAGER_LOADED           2 // Source Manager loaded - ready to establish a session
#define TW_STATE_SOURCEMANAGER_OPEN             3 // Source Manager open - session established, select source
#define TW_STATE_SOURCE_OPEN                    4 // Data Source open
#define TW_STATE_SOURCE_ENABLED                 5 // Data Source enabled - if ShowUI then Source User Interface else Source begins data acquisition process
#define TW_STATE_XFER_READY                     6 // Transfer ready - Dats Source ready to transfer images
#define TW_STATE_XFERRING                       7 // Data is transferred

#define TW_APP_MANUFACTURER "Skan Project"
#define TW_APP_PRODUCTFAMILY "Skan Extension Plugin"

static DWORD WINAPI threadStart( LPVOID lpParam );
static LRESULT CALLBACK _wndproc( HWND, UINT, WPARAM, LPARAM );

static bool g_wndClassInitialized = false;

#define TMSG_SCAN 0xAFA1
#define TMSG_CONNECT 0xAFA2
#define TMSG_DISCONNECT 0xAFA3

// used for opening DSM
#ifndef TWH_CMP_MSC
extern TW_ENTRYPOINT g_dsmEntryPoint;
#endif
bool g_useCallbacks = false;		// this gets updated to true if the DSM is ver2

extern char g_applicationName[];

// this is the callback function to register for this app. it will be called by the source when its ready to start the transfer.
extern 
    //#ifdef TWH_CMP_MSC
    TW_UINT16 FAR PASCAL
    //#else
    //	FAR PASCAL TW_UINT16 
    //#endif
    DSMCallback( pTW_IDENTITY _pOrigin, pTW_IDENTITY _pDest, TW_UINT32 _DG, TW_UINT16 _DAT, TW_UINT16 _MSG, TW_MEMREF _pData );


char TwainModel::m_modelTypeName[] = "TWAIN";

TwainModel::TwainModel( ImageSite* imageSite, MessageListener* messageListener )
    : m_dsmState( 1 ),
    m_pDataSource( NULL ),
    m_dsMessage( -1 ),
    m_gotDataSources( false ),
    m_showDeviceUI( false ),
    m_deviceIndex( 0 ),
    m_imageSite( NULL ),
    m_currentDevice( NULL ),
    m_parentWindow( NULL ),
    m_contrast( 0 ), m_autoBright( false ), m_pageWidth( 0 ), m_pageHeight( 0 ), m_pixelType( PixelType::Default ), m_resolution( 0 ), m_pageSize( PageSize::Default ), m_orientation( 0 ),
    m_connected( false ),
    m_isFlatbedSupported( false ),
    m_isFeederSupported( false ),
    m_isDuplexSupported( false ),
    m_isPaperSourceFixed( false ),
    m_hEvent( NULL ),
    m_scanResult( false ),
    m_duplex( false )
{
    m_imageSite = imageSite;
    m_messageListener = messageListener;

    m_xferNum = 0;

    ::memset( &m_defaultDataSource, 0, sizeof( TW_IDENTITY ) );

    fillIdentity( m_identity, g_applicationName );

    m_hWnd2 = ::GetDesktopWindow();

    //m_dsmConnected = connectDSM();

    if ( !::loadDSMLibrary() )
    {
        // could not load the DSM
        //return 0;   /* todo: are we sure of this return? */
        throw "CANNOT LOAD DSM LIBRARY";
    }

    // create event
    if ( !( m_hEvent = ::CreateEvent( NULL, TRUE, FALSE, NULL ) ) )
    {
        //throw "Cannot create lock event!";
        return;
    }

    if ( ( m_hThread = ::CreateThread( NULL, 0, ::threadStart, this, 0, &m_threadId ) ) == NULL )
    {
        throw "THREAD!";
        return;
    }

    waitForEvent();

    return;
}

TwainModel::~TwainModel()
{
    ::PostThreadMessage( m_threadId, WM_QUIT, 0, 0 );
    //::Sleep( 10 );

    if ( m_hThread != NULL ) ::CloseHandle( m_hThread );
    if ( m_hEvent ) ::CloseHandle( m_hEvent );

    disconnectDSM();

    ::unloadDSMLibrary();
}

void TwainModel::waitForEvent()
{
    bool proceed = true;

    while( proceed )
    {
        DWORD waitResult = ::WaitForSingleObject( m_hEvent, 50L );

        switch ( waitResult )
        {
        case WAIT_OBJECT_0:
            {
                proceed = false;
                ::ResetEvent( m_hEvent );
            }
            break;

        case WAIT_TIMEOUT: break;
        }
    }
}

void TwainModel::onTwainError( ResultCode errorCode )
{
    m_messageListener->onError( errorCode );
}

void TwainModel::setIdentity( char* productName )
{
    // fill our identity structure
    fillIdentity( m_identity, productName );
}

void TwainModel::fillIdentity( TW_IDENTITY& identity, TW_STR32 productName )
{
    identity.Id = 0;
    identity.Version.MajorNum = 0;
    identity.Version.MinorNum = 6;
    identity.Version.Language = 2;//TWLG_USERLOCALE;
    identity.Version.Country = TWCY_USA;
    ::strncpy_s( identity.Version.Info, sizeof( identity.Version.Info ), "0.6.7", _TRUNCATE );
    identity.ProtocolMajor = TWON_PROTOCOLMAJOR;
    identity.ProtocolMinor = TWON_PROTOCOLMINOR;
#ifndef DF_APP2
    identity.SupportedGroups = DG_IMAGE | DG_CONTROL;
#else
    identity.SupportedGroups = DF_APP2 | DG_IMAGE | DG_CONTROL;
#endif

    ::strncpy_s( identity.Manufacturer, sizeof( identity.Manufacturer ), TW_APP_MANUFACTURER, _TRUNCATE );
    ::strncpy_s( identity.ProductFamily, sizeof( identity.ProductFamily ), TW_APP_PRODUCTFAMILY, _TRUNCATE );
    ::strncpy_s( identity.ProductName, sizeof( identity.ProductName ), productName, _TRUNCATE );//TW_APP_PRODUCTNAME );

    return;
}

TW_UINT16 TwainModel::dsmEntry( TW_UINT32 dg,TW_UINT16 dat, TW_UINT16 msg, TW_MEMREF pData )
{
    return ::_DSM_Entry( &m_identity, m_pDataSource, dg, dat, msg, pData );
}

TW_UINT16 TwainModel::dsmEntry( pTW_IDENTITY pDest, TW_UINT32 dg,TW_UINT16 dat, TW_UINT16 msg, TW_MEMREF pData )
{
    return ::_DSM_Entry( &m_identity, pDest, dg, dat, msg, pData );
}

void TwainModel::stop()
{
    if ( m_dsmState >= 3 )
    {
        // if we have selected a source, then it is possible it is open
        if ( m_pDataSource != NULL && m_dsmState >= 4 )
        {
            if ( m_dsmState >= 5 )
            {
                disableDS();

                // if DisableDS succeeds then m_dsmState will be set down to 4
                // if it did NOT succeed, try to cancel any pending transfers.
                if ( 5 <= m_dsmState )
                {
                    TW_PENDINGXFERS pendxfers = { 0 };
                    dsmEntry( DG_CONTROL, DAT_PENDINGXFERS, MSG_ENDXFER, ( TW_MEMREF )&pendxfers );

                    // we need to get rid of any pending transfers
                    if ( pendxfers.Count )
                    {
                        ::memset( &pendxfers, 0, sizeof( pendxfers ) );
                        dsmEntry( DG_CONTROL, DAT_PENDINGXFERS, MSG_RESET, ( TW_MEMREF )&pendxfers );
                    }

                    m_dsmState = 5;

                    disableDS();
                }
            }
            disconnect();
        }
    }
}

// TODO: have better error handling, look into if we need to cleanup on failures
bool TwainModel::connectDSM()
{
    if ( m_dsmState > 3 )
    {
        this->onTwainError( ResultCode::DriverDsmNotClosed ); // the DSM has already been opened, close it first
        return false;
    }

    if ( !::loadDSMLibrary() )
    {
        this->onTwainError( ResultCode::DriverDsmLoadFailed ); // the DSM could not be loaded, ensure that it is installed.
        return  false;
    }
    else
    {
        m_dsmState = 2;
    }

    TW_UINT16 ret = 0;

    HWND hWnd = m_hWnd2; //::GetDesktopWindow();
    if ( ( ret = dsmEntry( NULL, DG_CONTROL, DAT_PARENT, MSG_OPENDSM, ( TW_MEMREF ) &hWnd ) ) != TWRC_SUCCESS ) //( TW_MEMREF )&m_Parent ) ) )
    {
        TW_INT16 cc = 0;
        ret = this->getTWCC( NULL, cc );
        this->onTwainError( ResultCode::DriverDsmOpenFailed ); // OPEN DSM failed
        return false;
    }

#ifndef TWH_CMP_MSC
    // check for DSM2 support
    if ( ( m_identity.SupportedGroups & DF_DSM2 ) == DF_DSM2 )
    {
        g_dsmEntryPoint.Size = sizeof( TW_ENTRYPOINT );

        // do a MSG_GET to fill our entrypoints
        if ( TWRC_SUCCESS != ( ret = dsmEntry( NULL, DG_CONTROL, DAT_ENTRYPOINT, MSG_GET, ( pTW_ENTRYPOINT ) &g_dsmEntryPoint ) ) )
        {
            this->onTwainError( ERROR_PLUGIN_DRIVER_DSM_ENTRY ); // failed retrieving entry points
            return;
        }
    }
#endif

    m_dsmState = 3; // successfully opened the DSM

    return true;
}

void TwainModel::disconnectDSM()
{
    if ( m_dsmState < 3 )
    {
        //this->onTwainError( ERROR_DSM_NOT_OPEN );
        return;
    }

    TW_UINT16 ret = dsmEntry( NULL, DG_CONTROL, DAT_PARENT, MSG_CLOSEDSM, NULL );//( TW_MEMREF )&m_Parent );

    if ( ret == TWRC_SUCCESS ) // successfully closed the DSM
    {
        m_dsmState = 2;
    }
    else
    {
        this->onTwainError( ResultCode::DriverDsmEntry );
    }

    return;
}

pTW_IDENTITY TwainModel::getDefaultDataSource()
{
    if ( m_dsmState < 3 )
    {
        this->onTwainError( ResultCode::DriverDsmNotOpen );
        return NULL;
    }

    // get default

    ::memset( &m_defaultDataSource, 0, sizeof( TW_IDENTITY ) );

    TW_UINT16 twrc;

    twrc = dsmEntry( NULL, DG_CONTROL, DAT_IDENTITY, MSG_GETDEFAULT, ( TW_MEMREF ) &m_defaultDataSource );

    switch ( twrc )
    {
    case TWRC_SUCCESS:
        break;

    case TWRC_FAILURE:
        // failed to get the data source info
        this->onTwainError( ResultCode::DriverDsGetInfo );
        break;
    }

    return &m_defaultDataSource;
}

bool TwainModel::loadDS()
{
    // the application must be in state 3 to open a Data Source.
    if ( m_dsmState < 3 )
    {
        // the DSM needs to be opened first
        return false;
    }
    else if ( m_dsmState > 3 )
    {
        // a source has already been opened, please close it first
        return false;
    }

    if ( m_deviceIndex > 0 && m_deviceIndex <= ( uint_t ) m_dataSources.count() )
    {
        m_pDataSource = &( m_dataSources[ m_deviceIndex - 1 ] );
    }
    else
    {
        // open the default data source
        ::memset( &m_defaultDataSource, 0, sizeof( TW_IDENTITY ) );
        m_pDataSource = &m_defaultDataSource;
    }

#ifndef TWH_CMP_MSC
    TW_CALLBACK callback = { 0 };
#endif
    // open the specific data source
    TW_UINT16 twrc;
    twrc = dsmEntry( NULL, DG_CONTROL, DAT_IDENTITY, MSG_OPENDS, ( TW_MEMREF ) m_pDataSource );

    switch ( twrc )
    {
    case TWRC_SUCCESS:
        // Data source successfully opened!
        // Transition application to state 4
        m_dsmState = 4;

#ifndef TWH_CMP_MSC
        callback.CallBackProc = ( TW_MEMREF )DSMCallback;

        /* RefCon, On 32bit Could be used to store a pointer to this class to help passing the message on to be processed.
        But RefCon is too small to store a pointer on 64bit. For 64bit RefCon could storing an index to some global memory array.
        But if there is only one instance of the Application Class connecting to the DSM then the single global pointer to the application class can be used, and the RefCon can be ignored as we do here. */
        callback.RefCon = 0; 

        if ( TWRC_SUCCESS != ( twrc = dsmEntry( DG_CONTROL, DAT_CALLBACK, MSG_REGISTER_CALLBACK, ( TW_MEMREF )&callback ) ) )
        {
            // <DG_CONTROL / DAT_CALLBACK / MSG_REGISTER_CALLBACK> failed
        }
        else
        {
            g_useCallbacks = true;
        }
#endif
        break;

    default:
        this->onTwainError( ResultCode::DriverDsOpen ); // failed to open data source
        m_pDataSource = 0;
        break;
    }

    return ( m_dsmState == 4 );
}

void TwainModel::unloadDS()
{
    if ( m_dsmState < 4 )
    {
        // open a data source first
        return;
    }

    TW_UINT16 twrc = dsmEntry( NULL, DG_CONTROL, DAT_IDENTITY, MSG_CLOSEDS, ( TW_MEMREF ) m_pDataSource );

    switch ( twrc )
    {
    case TWRC_SUCCESS: // data source successfully closed
        // set application to state 3
        m_dsmState = 3;

        // reset the active source pointer
        m_pDataSource = NULL;
        break;

    default:
        this->onTwainError( ResultCode::DriverDsClose ); // failed to close data source
        break;
    }

    return;
}

void TwainModel::getDataSources()
{
    m_dataSources.clear( false );

    if ( m_dsmState < 3 )
    {
        // need to open the DSM first.
        return;
    }

    // get the first source
    TW_IDENTITY dataSource = { 0 };

    //unsigned int index = 0;

    TW_UINT16 twrc;

    twrc = dsmEntry( NULL, DG_CONTROL, DAT_IDENTITY, MSG_GETFIRST, ( TW_MEMREF ) &dataSource );

    switch ( twrc )
    {
    case TWRC_SUCCESS:
        m_dataSources.add( dataSource );
        break;

    case TWRC_FAILURE:
        this->onTwainError( ResultCode::DriverDsGetInfo ); // failed to get the first data source info
        return;
        break;

    case TWRC_ENDOFLIST:
        return;
        break;
    }

    // get the rest of the sources
    do
    {
        ::memset( &dataSource, 0, sizeof( TW_IDENTITY ) );

        twrc = dsmEntry( NULL, DG_CONTROL, DAT_IDENTITY, MSG_GETNEXT, ( TW_MEMREF ) &dataSource );

        switch ( twrc )
        {
        case TWRC_SUCCESS:
            m_dataSources.add( dataSource );
            break;

        case TWRC_FAILURE:
            this->onTwainError( ResultCode::DriverDsGetInfo ); // failed to get the rest of data source info
            return;
            //break;

        case TWRC_ENDOFLIST:
            return;
            //break;
        }
    }
    while ( twrc == TWRC_SUCCESS );

    m_gotDataSources = true;

    return;
}

TW_UINT16 TwainModel::getTWCC( pTW_IDENTITY pDestId, TW_INT16& cc )
{
    TW_STATUS status;
    ::memset( &status, 0, sizeof( TW_STATUS ) );

    TW_UINT16 twrc = dsmEntry( pDestId, DG_CONTROL, DAT_STATUS, MSG_GET, ( TW_MEMREF ) &status );

    if ( TWRC_SUCCESS == twrc )
    {
        cc = status.ConditionCode;
    }

    return twrc;
}

bool TwainModel::enableDS( TW_HANDLE hWnd, bool showUI, bool modalUI, bool uiOnly )
{
    bool result = true;

    if ( m_dsmState < 4 )
    {
        // open the data source first
        return false;
    }

    m_ui.ShowUI = 0;//uiOnly ? 1 : ( showUI ? 1 : 0 );
    m_ui.ModalUI = 0;//uiOnly ? 1 : ( modalUI ? 1 : 0 );
    m_ui.hParent = hWnd;

    TW_UINT16 twrc = dsmEntry( DG_CONTROL, DAT_USERINTERFACE, MSG_ENABLEDS, ( TW_MEMREF ) &m_ui );
    //TW_UINT16 twrc = dsmEntry( DG_CONTROL, DAT_USERINTERFACE, uiOnly ? MSG_ENABLEDSUIONLY : MSG_ENABLEDS, ( TW_MEMREF ) &m_ui );
    //TW_UINT16 twrc = dsmEntry( DG_CONTROL, DAT_USERINTERFACE, MSG_ENABLEDSUIONLY, ( TW_MEMREF ) &m_ui );//( TW_MEMREF ) &m_ui );
    //TW_UINT16 twrc = dsmEntry( DG_CONTROL, DAT_USERINTERFACE, MSG_ENABLEDS, ( TW_MEMREF ) &m_ui );

    if ( twrc != TWRC_SUCCESS && twrc != TWRC_CHECKSTATUS )
    {
        // cannot enable source
        m_dsmState = 4;
        TW_INT16 cc = 0;
        this->getTWCC( m_pDataSource, cc );
        result = false;
    }

    m_dsmState = 5;

    // Usually at this point the application sits here and waits for the scan to start.
    // We are notified that we can start a scan through the DSM's callback mechanism. The callback was registered when the DSM was opened.
    // If callbacks are not being used, then the DSM will be polled to see when the DS is ready to start scanning.

    return result;
}

bool TwainModel::enableDS( TW_HANDLE hWnd )
{
    return enableDS( hWnd, m_showDeviceUI, m_showDeviceUI, false ); // uiOnly not supported yet
}

void TwainModel::disableDS()
{
    if ( m_dsmState < 5 )
    {
        // need to enable the data source first
        return;
    }

    TW_UINT16 twrc = dsmEntry( DG_CONTROL, DAT_USERINTERFACE, MSG_DISABLEDS, ( TW_MEMREF )&( m_ui ) );

    if ( twrc == TWRC_SUCCESS )
    {
        m_dsmState = 4;
    }
    else
    {
        // cannot disable source
    }

    return;
}

void TwainModel::initiateTransfer_Native()
{
    bool xfersPending = true;
    TW_UINT16 twrc = TWRC_SUCCESS;

    while( xfersPending )
    {
        m_xferNum++;

        // update image info
        TW_IMAGEINFO imageInfo = { 0 };
        twrc = dsmEntry( DG_IMAGE, DAT_IMAGEINFO, MSG_GET, ( TW_MEMREF ) &imageInfo );

        if ( twrc != TWRC_SUCCESS )
        {
            break; // TODO: error handling
        }

        TW_MEMREF hImg = 0;

        // starting the transfer...
        twrc = dsmEntry( DG_IMAGE, DAT_IMAGENATIVEXFER, MSG_GET, ( TW_MEMREF ) &hImg );

        if ( twrc == TWRC_XFERDONE )
        {
            LPBITMAPINFOHEADER pDIB = ( LPBITMAPINFOHEADER ) _DSM_LockMemory( hImg );

            if ( pDIB == NULL )
            {
                // unable to lock memory, transfer failed
                break;
            }

            LONG width = pDIB->biWidth;
            LONG height = pDIB->biHeight;

            unsigned int paletteSize = 0;

            switch( pDIB->biBitCount )
            {
            case 1: paletteSize = sizeof( RGBQUAD ) << 1; break;
            case 8: paletteSize = sizeof( RGBQUAD ) << 8; break;
            case 24: break;
            default:
                assert( 0 ); // not going to work
                break;
            }

            // calculate the correct size
            size_t lineBitCount = width * pDIB->biBitCount; // number of actual image bits in a scanline
            size_t stride = ( ( lineBitCount + 31 ) & ( ~31 ) ) >> 3; // number of bytes in 32-bit aligned scanline
            size_t alignedBytes = stride * height; // size of aligned image data
            size_t lineColorCount = ( ( ( lineBitCount + 7 ) & ~7 ) >> 3 ); // number of bytes in a line
            size_t colorBytes = lineColorCount * height; // size of unaligned image data

            size_t headerSize = sizeof( BITMAPINFOHEADER ) + paletteSize;

            size_t sourceDibSize = headerSize + pDIB->biSizeImage;
            size_t destDibSize = headerSize + alignedBytes;

            // compute biSizeImage field value if not filled by the driver
            //if ( pDIB->biSizeImage == 0 )
            //{
            //	pDIB->biSizeImage = alignedBytes;

            //	// If a compression scheme is used the result may infact be larger. Increase the size to account for this.
            //	/*
            //	if ( pDIB->biCompression != 0 )
            //	{
            //	pDIB->biSizeImage = ( pDIB->biSizeImage * 3 ) / 2;
            //	}
            //	*/
            //}
            //else if ( pDIB->biSizeImage != alignedBytes && pDIB->biSizeImage == imageBytes )
            //{
            //	
            //}
            //else
            //{
            //	//throw "WRONG BITMAP SIZE!!";
            //	//MessageBoxA( NULL, "WRONG IMAGE SIZE!!", NULL, MB_OK );
            //	imageValid = false;
            //}

            bool aligned = ( pDIB->biSizeImage == 0 && pDIB->biCompression != 0 ) || ( pDIB->biSizeImage == alignedBytes );
            bool unaligned = alignedBytes != colorBytes && pDIB->biSizeImage == colorBytes;
            bool imageValid = aligned || unaligned;

            Pixels image = { 0 };

            if ( imageValid )
            {
                if ( unaligned )
                {
                    pDIB->biSizeImage = alignedBytes;
                }

                bool result = m_imageSite->createPixels( pDIB, image );
            }

            ::_DSM_UnlockMemory( hImg );
            ::_DSM_Free( hImg );
            pDIB = NULL;

            // notify the image site
            m_imageSite->imageReady( image );

            //updateEXTIMAGEINFO(); // TODO: 

            // see if there are any more image transfers to do

            bool cancel = m_imageSite->getScanStop();

            TW_PENDINGXFERS pendxfers = { 0 };

            if ( !cancel )
            {
                twrc = dsmEntry( DG_CONTROL, DAT_PENDINGXFERS, MSG_ENDXFER, ( TW_MEMREF ) &pendxfers );

                if ( twrc == TWRC_SUCCESS )
                {
                    // remaining images to transfer: pendxfers.Count

                    if ( pendxfers.Count == 0 )
                    {
                        // nothing left to transfer, finished
                        xfersPending = false;
                    }
                }
                else
                {
                    // failed to properly end the transfer
                    xfersPending = false;
                }
            }
            else
            {
                //twrc = dsmEntry( DG_CONTROL, DAT_PENDINGXFERS, MSG_STOPFEEDER, ( TW_MEMREF ) &pendxfers ); // TODO: make for twain 1.7
                xfersPending = false;

                m_imageSite->setScanStop( false );
            }
        }
        else if ( twrc == TWRC_CANCEL )
        {
            // canceled transfer image
            break;
        }
        else if ( twrc == TWRC_FAILURE )
        {
            // failed to transfer image
            break;
        }
    }

    // Check to see if we left the scan loop before we were actualy done scanning.
    // This will happen if we had an error. Need to let the DS know we are not going to transfer more images
    if ( xfersPending )
    {
        // Stop any transfer we may have started but could not finish...
        TW_PENDINGXFERS pendxfers = { 0 };

        twrc = dsmEntry( DG_CONTROL, DAT_PENDINGXFERS, MSG_ENDXFER, ( TW_MEMREF )&pendxfers );

        // We need to get rid of any pending transfers
        if ( pendxfers.Count != 0 )
        {
            ::memset( &pendxfers, 0, sizeof( pendxfers ) );

            dsmEntry( DG_CONTROL, DAT_PENDINGXFERS, MSG_RESET, ( TW_MEMREF )&pendxfers );
        }
    }

    // adjust our state now that the scanning session is done
    m_dsmState = 5;

    //m_imageSite->scanComplete();

    return;
}

pTW_IDENTITY TwainModel::getAppIdentity()
{
    return &m_identity;
}

template<typename T, typename TW>
T* toArray( TW* itemList, int count )
{
    T* values = new T[ count ];
    for ( int i = 0; i < count; i++ ) values[ i ] = static_cast<T>( itemList[ i ] );

    return values;
}

bool* toArray( TW_BOOL* itemList, int count )
{
    bool* values = new bool[ count ];
    for ( int i = 0; i < count; i++ ) values[ i ] = ( itemList[ i ] != 0 );

    return values;
}

bool TwainModel::getCap( int capId, BaseCap*& result )
{
    if ( m_dsmState < 4 )
    {
        // you need to open a data source first
        return false;
    }

    TW_CAPABILITY cap = { 0 };
    cap.Cap = capId;
    cap.ConType = TWON_DONTCARE16;

    TW_UINT16 twrc = dsmEntry( DG_CONTROL, DAT_CAPABILITY, MSG_GET, ( TW_MEMREF )&cap );

    switch( twrc )
    {
    case TWRC_FAILURE:
        return false;
    }

    TW_MEMREF pContainer = ::_DSM_LockMemory( cap.hContainer );

    switch ( cap.ConType )
    {
    case TWON_ONEVALUE:
        {
            pTW_ONEVALUE pValue = ( pTW_ONEVALUE ) pContainer;
            switch( pValue->ItemType )
            {
            case TWTY_BOOL: result = new BoolValueCap( capId, ( *( ( TW_BOOL* ) &pValue->Item ) ) != 0 ); break;
            case TWTY_INT8: result = new IntValueCap( capId, ( int )( *( ( TW_INT8* ) &pValue->Item ) ) ); break;
            case TWTY_INT16: result = new IntValueCap( capId, ( int )( *( ( TW_INT16* ) &pValue->Item ) ) ); break;
            case TWTY_INT32: result = new IntValueCap( capId, ( int )( *( ( TW_INT32* ) &pValue->Item ) ) ); break;
            case TWTY_UINT8: result = new IntValueCap( capId, ( int )( *( ( TW_UINT8* ) &pValue->Item ) ) ); break;
            case TWTY_UINT16: result = new IntValueCap( capId, ( int )( *( ( TW_UINT16* ) &pValue->Item ) ) ); break;
            case TWTY_UINT32: result = new IntValueCap( capId, ( int )( *( ( TW_UINT32* ) &pValue->Item ) ) ); break;
            case TWTY_FIX32: result = new FloatValueCap( capId, ::fix32ToFloat( *( ( TW_FIX32* ) &pValue->Item ) ) ); break;
            }
        }
        break;

    case TWON_ENUMERATION:
        {
            pTW_ENUMERATION pEnum = ( pTW_ENUMERATION ) pContainer;
            TW_UINT32 count = pEnum->NumItems;
            TW_UINT32 currentIndex = pEnum->CurrentIndex;
            TW_UINT32 defaultIndex = pEnum->DefaultIndex;

            switch ( pEnum->ItemType )
            {
            case TWTY_BOOL: result = new BoolListCap( capId, count, toArray( ( TW_BOOL* ) &pEnum->ItemList, count ), currentIndex, defaultIndex ); break;

            case TWTY_INT8: result = new IntListCap( capId, count, toArray<int, TW_INT8>( ( TW_INT8* ) &pEnum->ItemList, count ), currentIndex, defaultIndex ); break;

            case TWTY_UINT8: result = new IntListCap( capId, count, toArray<int, TW_UINT8>( ( TW_UINT8* ) &pEnum->ItemList, count ), currentIndex, defaultIndex ); break;

            case TWTY_INT16: result = new IntListCap( capId, count, toArray<int, TW_INT16>( ( TW_INT16* ) &pEnum->ItemList, count ), currentIndex, defaultIndex ); break;

            case TWTY_UINT16: result = new IntListCap( capId, count, toArray<int, TW_UINT16>( ( TW_UINT16* ) &pEnum->ItemList, count ), currentIndex, defaultIndex ); break;

            case TWTY_INT32: result = new IntListCap( capId, count, toArray<int, TW_INT32>( ( TW_INT32* ) &pEnum->ItemList, count ), currentIndex, defaultIndex ); break;

            case TWTY_UINT32: result = new IntListCap( capId, count, toArray<int, TW_UINT32>( ( TW_UINT32* ) &pEnum->ItemList, count ), currentIndex, defaultIndex ); break;

            case TWTY_FIX32:
                {
                    TW_FIX32* array = ( TW_FIX32* ) &pEnum->ItemList;

                    float* values = new float[ count ];

                    for ( TW_UINT32 i = 0; i < count; i++ ) values[ i ] = ::fix32ToFloat( array[ i ] );

                    result = new FloatListCap( capId, count, values, currentIndex, defaultIndex );
                }
                break;
            }
        }
        break;

    case TWON_RANGE:
        {
            pTW_RANGE pRange = ( pTW_RANGE ) pContainer;

            switch ( pRange->ItemType )
            {
            case TWTY_BOOL:
                assert( 0 );
                break;
            case TWTY_INT8:
                result = new IntRangeCap( capId, ( int )( *( TW_INT8* )&pRange->MinValue ), ( int )( *( TW_INT8* )&pRange->MaxValue ), ( int )( *( TW_INT8* )&pRange->StepSize ), ( int )( *( TW_INT8* )&pRange->DefaultValue ), ( int )( *( TW_INT8* )&pRange->CurrentValue ) );
                break;
            case TWTY_INT16:
                result = new IntRangeCap( capId, ( int )( *( TW_INT16* )&pRange->MinValue ), ( int )( *( TW_INT16* )&pRange->MaxValue ), ( int )( *( TW_INT16* )&pRange->StepSize ), ( int )( *( TW_INT16* )&pRange->DefaultValue ), ( int )( *( TW_INT16* )&pRange->CurrentValue ) );
                break;
            case TWTY_INT32:
                result = new IntRangeCap( capId, ( int )( *( TW_INT32* )&pRange->MinValue ), ( int )( *( TW_INT32* )&pRange->MaxValue ), ( int )( *( TW_INT32* )&pRange->StepSize ), ( int )( *( TW_INT32* )&pRange->DefaultValue ), ( int )( *( TW_INT32* )&pRange->CurrentValue ) );
                break;
            case TWTY_UINT8:
                result = new IntRangeCap( capId, ( int )( *( TW_UINT8* )&pRange->MinValue ), ( int )( *( TW_UINT8* )&pRange->MaxValue ), ( int )( *( TW_UINT8* )&pRange->StepSize ), ( int )( *( TW_UINT8* )&pRange->DefaultValue ), ( int )( *( TW_UINT8* )&pRange->CurrentValue ) );
                break;
            case TWTY_UINT16:
                result = new IntRangeCap( capId, ( int )( *( TW_UINT16* )&pRange->MinValue ), ( int )( *( TW_UINT16* )&pRange->MaxValue ), ( int )( *( TW_UINT16* )&pRange->StepSize ), ( int )( *( TW_UINT16* )&pRange->DefaultValue ), ( int )( *( TW_UINT16* )&pRange->CurrentValue ) );
                break;
            case TWTY_UINT32:
                result = new IntRangeCap( capId, ( int )( *( TW_UINT32* )&pRange->MinValue ), ( int )( *( TW_UINT32* )&pRange->MaxValue ), ( int )( *( TW_UINT32* )&pRange->StepSize ), ( int )( *( TW_UINT32* )&pRange->DefaultValue ), ( int )( *( TW_UINT32* )&pRange->CurrentValue ) );
                break;
            case TWTY_FIX32:
                result = new FloatRangeCap( capId, fix32ToFloat( *( TW_FIX32* )&pRange->MinValue ), fix32ToFloat( *( TW_FIX32* )&pRange->MaxValue ), fix32ToFloat( *( TW_FIX32* )&pRange->StepSize ), fix32ToFloat( *( TW_FIX32* )&pRange->DefaultValue ), ::fix32ToFloat( *( TW_FIX32* )&pRange->CurrentValue ) );
                break;
            }
        }
        break;

    case TWON_ARRAY:
        {
            pTW_ARRAY pArray = ( pTW_ARRAY ) pContainer;
            TW_UINT32 count = pArray->NumItems;

            switch ( pArray->ItemType )
            {
            case TWTY_BOOL: result = new BoolListCap( capId, count, toArray( ( TW_BOOL* ) &pArray->ItemList, count ) ); break;
            case TWTY_INT8: result = new IntListCap( capId, count, toArray<int, TW_INT8>( ( TW_INT8* ) &pArray->ItemList, count ) ); break;
            case TWTY_UINT8: result = new IntListCap( capId, count, toArray<int, TW_UINT8>( ( TW_UINT8* ) &pArray->ItemList, count ) ); break;
            case TWTY_INT16: result = new IntListCap( capId, count, toArray<int, TW_INT16>( ( TW_INT16* ) &pArray->ItemList, count ) ); break;
            case TWTY_UINT16: result = new IntListCap( capId, count, toArray<int, TW_UINT16>( ( TW_UINT16* ) &pArray->ItemList, count ) ); break;
            case TWTY_INT32: result = new IntListCap( capId, count, toArray<int, TW_INT32>( ( TW_INT32* ) &pArray->ItemList, count ) ); break;
            case TWTY_UINT32: result = new IntListCap( capId, count, toArray<int, TW_UINT32>( ( TW_UINT32* ) &pArray->ItemList, count ) ); break;
            case TWTY_FIX32:
                {
                    TW_FIX32* array = ( TW_FIX32* ) &pArray->ItemList;

                    float* values = new float[ count ];

                    for ( TW_UINT32 i = 0; i < count; i++ ) values[ i ] = ::fix32ToFloat( array[ i ] );

                    result = new FloatListCap( capId, count, values );
                }
                break;
            }
        }
        break;
    }

    ::_DSM_UnlockMemory( cap.hContainer );

    ::_DSM_Free( cap.hContainer );

    return true;
}

TW_INT16 TwainModel::queryCapSupport( TW_UINT16 capId, TW_UINT32& pattern )
{
    pattern = 0;

    if ( m_dsmState < 4 )
    {
        // open a data source first
        throw "Open the data source first!";
        return TWCC_SEQERROR;
    }
    TW_CAPABILITY cap = { 0 };
    cap.Cap = capId;
    cap.ConType = TWON_ONEVALUE;

    TW_UINT16 twrc = dsmEntry( DG_CONTROL, DAT_CAPABILITY, MSG_QUERYSUPPORT, ( TW_MEMREF ) &cap );

    switch ( twrc )
    {
    case TWRC_FAILURE:
    default:
        {
            //throw "Query Support failed!";
        }
        break;

    case TWRC_SUCCESS:
        {
            if ( cap.ConType == TWON_ONEVALUE )
            {
                pTW_ONEVALUE pVal = ( pTW_ONEVALUE ) ::_DSM_LockMemory( cap.hContainer );

                pattern = pVal->Item;

                ::_DSM_UnlockMemory( cap.hContainer );
            }
            ::_DSM_Free( cap.hContainer );
        }
        break;
    }

    return twrc;
}	

TW_UINT16 TwainModel::setCapabilityValue( TW_UINT16 capId, const int value, TW_UINT16 type )
{
    TW_INT16 twrc = TWRC_FAILURE;
    TW_CAPABILITY cap;

    cap.Cap = capId;
    cap.ConType = TWON_ONEVALUE;
    cap.hContainer = ::_DSM_Alloc( sizeof( TW_ONEVALUE ) ); // largest int size

    if ( cap.hContainer == NULL )
    {
        // error allocating memory
        return twrc;
    }

    pTW_ONEVALUE pVal = ( pTW_ONEVALUE ) ::_DSM_LockMemory( cap.hContainer );
    pVal->ItemType = type;

    switch( type )
    {
    case TWTY_INT8:     *( TW_INT8* ) &pVal->Item = ( TW_INT8 ) value; break;
    case TWTY_INT16:    *( TW_INT16* ) &pVal->Item = ( TW_INT16 ) value; break;
    case TWTY_INT32:    *( TW_INT32* ) &pVal->Item = ( TW_INT32 ) value; break;
    case TWTY_UINT8:    *( TW_UINT8* ) &pVal->Item = ( TW_UINT8 ) value; break;
    case TWTY_UINT16:   *( TW_UINT16* ) &pVal->Item = ( TW_UINT16 ) value; break;
    case TWTY_UINT32:   *( TW_UINT32* ) &pVal->Item = ( TW_UINT32 ) value; break;
    case TWTY_BOOL:     *( TW_BOOL* ) &pVal->Item = ( TW_BOOL ) value; break;
    }

    // capability structure is set, make the call to the source now
    twrc = dsmEntry( DG_CONTROL, DAT_CAPABILITY, MSG_SET, ( TW_MEMREF )&( cap ) );

    TW_INT16 cc;
    if ( twrc == TWRC_CHECKSTATUS )
    {

    }
    else if ( twrc == TWRC_FAILURE )
    {
        // could not set the cap
        getTWCC( m_pDataSource, cc );
    }

    ::_DSM_UnlockMemory( cap.hContainer );
    ::_DSM_Free( cap.hContainer );

    return twrc;
}

TW_UINT16 TwainModel::setCapabilityValue( TW_UINT16 Cap, const pTW_FIX32 _pValue )
{
    TW_INT16 twrc = TWRC_FAILURE;
    TW_CAPABILITY cap;

    cap.Cap = Cap;
    cap.ConType = TWON_ONEVALUE;
    cap.hContainer = ::_DSM_Alloc( sizeof( TW_ONEVALUE_FIX32 ) );

    if ( 0 == cap.hContainer )
    {
        // error allocating memory
        return twrc;
    }

    pTW_ONEVALUE_FIX32 pVal = ( pTW_ONEVALUE_FIX32 ) ::_DSM_LockMemory( cap.hContainer );

    pVal->ItemType = TWTY_FIX32;
    pVal->Item = *_pValue;

    // capability structure is set, make the call to the source now
    twrc = dsmEntry( DG_CONTROL, DAT_CAPABILITY, MSG_SET, ( TW_MEMREF )&( cap ) );
    TW_INT16 cc;
    if ( twrc == TWRC_CHECKSTATUS )
    {

    }
    else if ( twrc == TWRC_FAILURE )
    {
        // could not set capability
        getTWCC( m_pDataSource, cc );
    }

    ::_DSM_UnlockMemory( cap.hContainer );
    ::_DSM_Free( cap.hContainer );

    return twrc;
}

TW_UINT16 TwainModel::setCapabilityValue( TW_UINT16 Cap, const pTW_FRAME _pValue )
{
    TW_INT16 twrc = TWRC_FAILURE;
    TW_CAPABILITY cap;

    cap.Cap = Cap;
    cap.ConType = TWON_ONEVALUE;
    cap.hContainer = ::_DSM_Alloc( sizeof( TW_ONEVALUE_FRAME ) );

    if ( cap.hContainer == 0 )
    {
        // error allocating memory
        return twrc;
    }

    pTW_ONEVALUE_FRAME pVal = ( pTW_ONEVALUE_FRAME ) ::_DSM_LockMemory( cap.hContainer );

    pVal->ItemType = TWTY_FRAME;
    pVal->Item = *_pValue;

    // capability structure is set, make the call to the source now
    twrc = dsmEntry( DG_CONTROL, DAT_CAPABILITY, MSG_SET, ( TW_MEMREF )&( cap ) );

    if ( twrc == TWRC_CHECKSTATUS )
    {

    }
    else if ( twrc == TWRC_FAILURE )
    {
        // could not set capability
    }

    ::_DSM_UnlockMemory( cap.hContainer );
    ::_DSM_Free( cap.hContainer );

    return twrc;
}

TW_UINT16 TwainModel::setCapabilityArray( TW_UINT16 Cap, const int* _pValues, int count, TW_UINT16 _type )
{
    TW_INT16 twrc = TWRC_FAILURE;
    TW_CAPABILITY cap;

    cap.Cap = Cap;
    cap.ConType = TWON_ARRAY;
    cap.hContainer = ::_DSM_Alloc( sizeof( TW_ARRAY ) + getTWTYsize( _type ) * count );// Largest int size
    if ( cap.hContainer == 0 )
    {
        // error allocating memory
        return twrc;
    }

    pTW_ARRAY pArray = ( pTW_ARRAY ) ::_DSM_LockMemory( cap.hContainer );

    pArray->ItemType = _type;
    pArray->NumItems = count;

#define CAPCASE( type ) case TWTY_##type: for ( i = 0; i < count; i++ ) { ( ( pTW_##type )( &pArray->ItemList ) )[ i ] = ( TW_##type )_pValues[ i ]; } break

    int i = 0;
    switch( _type )
    {
        CAPCASE( INT8 );
        CAPCASE( INT16 );
        CAPCASE( INT32 );
        CAPCASE( UINT8 );
        CAPCASE( UINT16 );
        CAPCASE( UINT32 );
        CAPCASE( BOOL );
    }

    // capability structure is set, make the call to the source now
    twrc = dsmEntry( DG_CONTROL, DAT_CAPABILITY, MSG_SET, ( TW_MEMREF )&( cap ) );

    if ( twrc == TWRC_CHECKSTATUS )
    {

    }
    else if ( twrc == TWRC_FAILURE )
    {
        // could not set capability
    }

    ::_DSM_UnlockMemory( cap.hContainer );
    ::_DSM_Free( cap.hContainer );
    return twrc;
}

void TwainModel::startScan()
{
    if ( m_dsmState != 6 )
    {
        //this->onTwainError( ERROR_SCAN_START ); // a scan cannot be initiated unless we are in state 6
        return;// NULL;
    }

    return initiateTransfer_Native();
}

void TwainModel::EnableDS()
{
    this->m_dsMessage = 0;

    HWND hWnd = m_hWnd2; //::GetDesktopWindow();

    // Enable the data source. This puts us in state 5 which means that we have to wait for the data source to tell us to move to state 6 and start the transfer. Once in state 5, no more set ops can be done on the caps, only get ops.
    // The scan will not start until the source calls the callback function that was registered earlier.

    if ( !enableDS( hWnd ) )
    {
        return;
    }

#ifdef _WINDOWS
    // now we have to wait until we hear something back from the DS.
    while( !m_dsMessage )
    {
        TW_EVENT twEvent = { 0 };

        // if we are using callbacks, there is nothing to do here except sleep and wait for our callback from the DS. If we are not using them, then we have to poll the DSM.

        // pumping messages is for Windows only
        MSG msg = { 0 };
        if ( !::GetMessage( ( LPMSG ) &msg, NULL, 0, 0 ) )
        {
            break;//WM_QUIT
        }
        twEvent.pEvent = ( TW_MEMREF ) &msg;

        twEvent.TWMessage = MSG_NULL;
        TW_UINT16 twrc = 0;

        twrc = dsmEntry( DG_CONTROL, DAT_EVENT, MSG_PROCESSEVENT, ( TW_MEMREF ) &twEvent );

        if ( !g_useCallbacks && twrc == TWRC_DSEVENT )
        {
            // check for message from Source
            switch ( twEvent.TWMessage )
            {
            case MSG_XFERREADY:
            case MSG_CLOSEDSREQ:
            case MSG_CLOSEDSOK:
            case MSG_NULL:
                this->m_dsMessage = twEvent.TWMessage;
                break;

            default:
                // Unknown message in MSG_PROCESSEVENT loop
                break;
            }
        }
        if ( twrc != TWRC_DSEVENT )
        {
            ::TranslateMessage( ( LPMSG ) &msg );
            ::DispatchMessage( ( LPMSG ) &msg );
        }
    }
#endif

    // at this point the source has sent us a callback saying that it is ready to transfer the image

    if ( this->m_dsMessage == MSG_XFERREADY )
    {
        // move to state 6 as a result of the data source. We can start a scan now.
        this->m_dsmState = 6;

        this->startScan();
    }

    this->disableDS(); // scan is done, disable the ds, thus moving us back to state 4 where we can negotiate caps again

    return;
}


// Callback function for DS. This is a callback function that will be called by the source when it is ready for the application to start a scan. This callback needs to be registered with the DSM before it can be called.
// It is important that the application returns right away after receiving this message. Set a flag and return. Do not process the callback in this function.

//#ifdef TWH_CMP_MSC
TW_UINT16 FAR PASCAL
    //#else
    //FAR PASCAL TW_UINT16 
    //#endif
    DSMCallback( pTW_IDENTITY pOrigin, pTW_IDENTITY pDest, TW_UINT32 dg, TW_UINT16 dat, TW_UINT16 msg, TW_MEMREF pData )
{
    TW_UINT16 twrc = TWRC_SUCCESS;

    // we are only waiting for callbacks from our datasource, so validate that the originator
    //if ( !pOrigin || pOrigin->Id != /*TwainModel::getInstance()*/ g_pTwainModel->getDataSource()->Id )
    {
        return TWRC_FAILURE;
    }
    switch ( msg )
    {
    case MSG_XFERREADY:
    case MSG_CLOSEDSREQ:
    case MSG_CLOSEDSOK:
    case MSG_NULL:
        break;

    default:
        //  unknown message in callback routine
        twrc = TWRC_FAILURE;
        break;
    }

    return twrc;
}

bool TwainModel::getDevices()
{
    if ( !m_dsmConnected ) return false;

    m_devices.clear( false );

    this->getDataSources();

    int deviceIndex = 1;
    size_t len = 0;

    for ( auto& item : m_dataSources )
    {
        DeviceInfo di = { 0 };

        di.index = deviceIndex;
        char idString[ 16 ] = { 0 };
        Convert::ToString( idString, item.Id );

        len = String::ToWchar( di.id, idString );
        len = String::ToWchar( di.uid, item.ProductName );
        len = String::ToWchar( di.manufacturer, item.Manufacturer );
        len = String::ToWchar( di.productFamily, item.ProductFamily );
        len = String::ToWchar( di.productName, item.ProductName );

        ImagingDevice device( &di );

        m_devices.add( device );
    }

    return true;
}

ResultCode TwainModel::scan()
{
    m_scanResult = 0;

    ::PostThreadMessage( m_threadId, TMSG_SCAN, 0, 0 );

    waitForEvent();

    return ( ResultCode ) m_scanResult;
}

ResultCode TwainModel::doScan()
{
    if ( !connect() ) return ResultCode::DriverConnection;

    TW_UINT16 twrc = 0;

    twrc = setCapabilityValue( ICAP_XFERMECH, TWSX_NATIVE, m_capXferMech.type );

    // TODO:
    //ICAP_AUTODISCARDBLANKPAGES

    if ( !m_isPaperSourceFixed )
    {
        twrc = setCapabilityValue( CAP_FEEDERENABLED, m_paperSource == PaperSource::Feeder ? 1 : 0, m_capFeederEnabled.type );
    }

    if ( m_isDuplexSupported )
    {
        twrc = setCapabilityValue( CAP_DUPLEXENABLED, m_duplex, m_capDuplexEnabled.type );
    }

    twrc = setCapabilityValue( CAP_XFERCOUNT, -1, TWTY_INT16 );

    twrc = setCapabilityValue( ICAP_UNITS, TWUN_INCHES, TWTY_UINT16 );

    if ( m_resolution > 0 )
    {
        TW_FIX32 fix32 = { m_resolution, 0 };
        twrc = setCapabilityValue( ICAP_XRESOLUTION, &fix32 );
        twrc = setCapabilityValue( ICAP_YRESOLUTION, &fix32 );
    }
    if ( m_pixelType != PixelType::Default )
    {
        twrc = setCapabilityValue( ICAP_PIXELTYPE, ( int ) m_pixelType, m_capPixelType.type );
    }

    if ( m_pageWidth <= 0 || m_pageHeight <= 0 || !m_capFrames.set )
    {
        twrc = setCapabilityValue( ICAP_SUPPORTEDSIZES, TWSS_NONE, TWTY_UINT16 );

        TW_IMAGELAYOUT il = { 0 };
        il.DocumentNumber = 1;
        il.FrameNumber = 1;
        il.PageNumber = 1;
        TW_FIX32 fix32PageWidth = ::floatToFIX32( m_pageWidth );
        TW_FIX32 fix32PageHeight = ::floatToFIX32( m_pageHeight );
        il.Frame.Right = fix32PageWidth;
        il.Frame.Bottom = fix32PageHeight;

        twrc = dsmEntry( DG_IMAGE, DAT_IMAGELAYOUT, MSG_SET, ( TW_MEMREF ) &il );

        if ( twrc == TWRC_SUCCESS )
        {
            twrc = dsmEntry( DG_IMAGE, DAT_IMAGELAYOUT, MSG_GET, ( TW_MEMREF ) &il );
        }

#define IS_ABOUT( f, wv, hv ) if ( m_pageWidth > ( wv - 0.1f ) && m_pageWidth < ( wv + 0.1f ) && m_pageHeight > ( hv - 0.1f ) && m_pageHeight < ( hv + 0.1f ) ) { size = f; }
#define ELSE_IS_ABOUT( f, wv, hv ) if ( m_pageWidth > ( wv - 0.1f ) && m_pageWidth < ( wv + 0.1f ) && m_pageHeight > ( hv - 0.1f ) && m_pageHeight < ( hv + 0.1f ) ) { size = f; }

        //if ( twrc != TWRC_SUCCESS )
        {
            TW_UINT16 size = TWSS_NONE;

            IS_ABOUT( TWSS_A4LETTER, 8.27f, 11.69f )
                ELSE_IS_ABOUT( TWSS_USLETTER, 8.5f, 11.0f )
                ELSE_IS_ABOUT( TWSS_B5LETTER, 6.9f, 9.8f )
                ELSE_IS_ABOUT( TWSS_USLEGAL, 8.5f, 14.0f )
                ELSE_IS_ABOUT( TWSS_A5, 5.83f, 8.27f )
                ELSE_IS_ABOUT( TWSS_B4, 9.84f, 13.9f )
                ELSE_IS_ABOUT( TWSS_B6, 4.92f, 6.93f )
                ELSE_IS_ABOUT( TWSS_USLEDGER, 17.0f, 11.0f )
                ELSE_IS_ABOUT( TWSS_USEXECUTIVE, 7.25f, 10.5f )
                ELSE_IS_ABOUT( TWSS_A3, 11.69f, 16.54f )
                ELSE_IS_ABOUT( TWSS_B3, 13.90f, 19.69f )
                ELSE_IS_ABOUT( TWSS_A6, 4.13f, 5.83f )
                ELSE_IS_ABOUT( TWSS_C4, 9.02f, 12.76f )
                ELSE_IS_ABOUT( TWSS_C5, 6.38, 9.02f )
                ELSE_IS_ABOUT( TWSS_C6, 4.49, 6.38f )
                //else IS_ABOUT( , f, f )

                if ( size == TWSS_NONE && twrc != TWRC_SUCCESS )
                {
                    size = TWSS_A4LETTER;
                }

                twrc = setCapabilityValue( ICAP_SUPPORTEDSIZES, size, TWTY_UINT16 );
        }
    }
    else
    {
        TW_FRAME frame = { 0 };
        frame.Right = ::floatToFIX32( m_pageWidth );
        frame.Bottom = ::floatToFIX32( m_pageHeight );

        twrc = setCapabilityValue( ICAP_FRAMES, &frame );
    }

#ifndef TWH_CMP_MSC
    twrc = setCapabilityValue( ICAP_AUTOSIZE, TWAS_AUTO, TWTY_UINT16 );
#endif
    //twrc = setCapabilityValue( ICAP_AUTOBRIGHT, m_autoBright ? 1 : 0, TWTY_BOOL );

    {
        TW_FIX32 fix32 = { ( TW_INT16 ) m_contrast, 0 };
        twrc = setCapabilityValue( ICAP_CONTRAST, &fix32 );
    }

    twrc = setCapabilityValue( ICAP_ORIENTATION, m_orientation, m_capOrientation.type );

    //TW_FIX32 fix32Rotation = { -270, 0 };
    //g_pTwainModel->set_ICAP_ROTATION( fix32Rotation );

    // set image file format
    //g_pTwainModel->set_ICAP_IMAGEFILEFORMAT( TWFF_JFIF );

    // set the image compression
    //g_pTwainModel->set_ICAP_COMPRESSION( TWCP_JPEG );

    this->EnableDS();

    disconnect();

    return ResultCode::Success;
}

BaseCap* TwainModel::getCap( unsigned int cap )
{
    BaseCap* baseCap = NULL;

    return getCap( cap, baseCap ) ? baseCap : NULL;
}

bool TwainModel::connect()
{
    if ( !m_dsmConnected ) return false;
    if ( m_connected ) return true;

    return ( m_connected = loadDS() );
}

void TwainModel::disconnect()
{
    if ( !m_dsmConnected || !m_connected ) return;

    this->unloadDS();

    m_connected = false;
}

int TwainModel::getDeviceCount()
{
    return m_devices.count();
}

void TwainModel::initCap( TW_UINT16 id, CapParams& c )
{
    if ( m_dsmState < 4 )
    {
        // you need to open a data source first
        return;
    }

    ::memset( &c, 0, sizeof( CapParams ) );
    c.id = id;

    TW_UINT32 supportPattern = 0;

    if ( queryCapSupport( id, supportPattern ) == TWRC_SUCCESS )
    {
        c.get = ( supportPattern & TWQC_GET ) != 0;
        c.set = ( supportPattern & TWQC_SET ) != 0;
        c.getDefault = ( supportPattern & TWQC_GETDEFAULT ) != 0;
        c.getCurrent = ( supportPattern & TWQC_GETCURRENT ) != 0;
        c.reset = ( supportPattern & TWQC_RESET ) != 0;

        c.querySupported = true;
    }

    TW_CAPABILITY cap = { 0 };
    cap.Cap = id;
    cap.ConType = TWON_DONTCARE16;

    TW_UINT16 twrc = dsmEntry( DG_CONTROL, DAT_CAPABILITY, MSG_GET, ( TW_MEMREF ) &cap );

    switch( twrc )
    {
    case TWRC_SUCCESS:
        c.getSupported = true;
        break;
    case TWRC_FAILURE:
        return;
    }

    TW_MEMREF pContainer = ::_DSM_LockMemory( cap.hContainer );
    TW_UINT16 type = 0;

    switch ( cap.ConType )
    {
    case TWON_ONEVALUE:		type = ( ( pTW_ONEVALUE ) pContainer )->ItemType; break;
    case TWON_ENUMERATION:	type = ( ( pTW_ENUMERATION ) pContainer )->ItemType; break;
    case TWON_RANGE:		type = ( ( pTW_RANGE ) pContainer )->ItemType; break;
    case TWON_ARRAY:		type = ( ( pTW_ARRAY ) pContainer )->ItemType; break;
    }

    c.type = type;

    ::_DSM_UnlockMemory( cap.hContainer );
    ::_DSM_Free( cap.hContainer );

    return;
}

bool TwainModel::getCapabilityIntValue( TW_UINT16 id, int& value )
{
    value = 0;

    TW_CAPABILITY cap = { 0 };
    cap.Cap = id;
    cap.ConType = TWON_DONTCARE16;

    TW_UINT16 twrc = dsmEntry( DG_CONTROL, DAT_CAPABILITY, MSG_GET, ( TW_MEMREF ) &cap );

    TW_MEMREF pContainer = ::_DSM_LockMemory( cap.hContainer );

    if ( cap.ConType == TWON_ONEVALUE )
    {
        pTW_ONEVALUE pValue = ( pTW_ONEVALUE ) pContainer;
        switch ( pValue->ItemType )
        {
        case TWTY_BOOL: value = ( int ) ( *( ( TW_BOOL* ) &pValue->Item ) ); break;
        case TWTY_INT8: value = ( int ) ( *( ( TW_INT8* ) &pValue->Item ) ); break;
        case TWTY_INT16: value = ( int ) ( *( ( TW_INT16* ) &pValue->Item ) ); break;
        case TWTY_INT32: value = ( int ) ( *( ( TW_INT32* ) &pValue->Item ) ); break;
        case TWTY_UINT8: value = ( int ) ( *( ( TW_UINT8* ) &pValue->Item ) ); break;
        case TWTY_UINT16: value = ( int ) ( *( ( TW_UINT16* ) &pValue->Item ) ); break;
        case TWTY_UINT32: value = ( int ) ( *( ( TW_UINT32* ) &pValue->Item ) ); break;
        default:
            return false;
        }
    }
    else
    {
        return false;
    }

    ::_DSM_UnlockMemory( cap.hContainer );
    ::_DSM_Free( cap.hContainer );

    return true;
}

bool TwainModel::getIsDeviceFeatureSupported( DeviceFeature deviceFeature ) const
{
    switch ( deviceFeature )
    {
    case DeviceFeature::Flatbed: return m_isFlatbedSupported; break;
    case DeviceFeature::Feeder: return m_isFeederSupported; break;
    case DeviceFeature::Duplex: return m_isDuplexSupported; break;
    }
    return false;
}

long TwainModel::initializeDevice()
{
    if ( !m_dsmConnected ) return -1;

    bool alreadyConnected = m_connected;
    if ( !connect() ) return -1;

    initCap( ICAP_PIXELTYPE, m_capPixelType );
    initCap( ICAP_BITDEPTH, m_capBitDepth );
    initCap( CAP_FEEDERENABLED, m_capFeederEnabled );
    initCap( CAP_DUPLEX, m_capDuplex );
    initCap( CAP_DUPLEXENABLED, m_capDuplexEnabled );
    initCap( ICAP_XRESOLUTION, m_capResolution );
    initCap( ICAP_XFERMECH, m_capXferMech );
    initCap( ICAP_ORIENTATION, m_capOrientation );
    initCap( ICAP_ROTATION, m_capRotation );
    initCap( ICAP_FRAMES, m_capFrames );
    initCap( ICAP_SUPPORTEDSIZES, m_capSupportedSizes );
    initCap( ICAP_PHYSICALWIDTH, m_capPhysicalWidth );
    initCap( ICAP_PHYSICALHEIGHT, m_capPhysicalHeight );

    TW_UINT16 twrc = 0;
    int value = 0;

    // device features

    m_paperSource = PaperSource::None;
    m_isFlatbedSupported = m_isFeederSupported = m_isDuplexSupported = false;
    m_isPaperSourceFixed = false;

    bool allowFeederEnabledTrue = setCapabilityValue( CAP_FEEDERENABLED, 1, m_capFeederEnabled.type ) == TWRC_SUCCESS;
    bool allowFeederEnabledFalse = setCapabilityValue( CAP_FEEDERENABLED, 0, m_capFeederEnabled.type ) == TWRC_SUCCESS;

    m_isFeederSupported = ( m_capFeederEnabled.querySupported && m_capFeederEnabled.getSupported ) && allowFeederEnabledTrue;

    twrc = getCapabilityIntValue( CAP_FEEDERENABLED, value );

    m_isFlatbedSupported = m_capFeederEnabled.getSupported && ( value == 0 || allowFeederEnabledFalse );

    if ( ( m_isFlatbedSupported ? 1 : 0 ) + ( m_isFeederSupported ? 1 : 0 ) == 1 )
    {
        m_paperSource = m_isFlatbedSupported ? PaperSource::Flatbed : PaperSource::Feeder;
        m_isPaperSourceFixed = true;
    }

    m_isDuplexSupported = m_isFeederSupported && getCapabilityIntValue( CAP_DUPLEX, value ) && value != TWDX_NONE;

    if ( !alreadyConnected ) disconnect();

    return 0;
}

ImagingDevice* TwainModel::getDevice( int index )
{
    if ( index < 0 || index >= m_devices.count() ) return NULL;
    return &m_devices[ index ];
}

static LRESULT CALLBACK _wndproc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
    return DefWindowProc( hWnd, msg, wParam, lParam );
}

DWORD WINAPI threadStart( LPVOID lpParam )
{
    return ( ( TwainModel* ) lpParam )->doThread();
}

uint_t TwainModel::doThread()
{
    if ( !g_wndClassInitialized )
    {
        WNDCLASSEX wc = { 0 };
        wc.cbSize = sizeof( wc );
        wc.lpszClassName = L"SkanTwainMessageWindowClass";
        wc.hInstance = NULL;
        wc.lpfnWndProc = _wndproc;

        ATOM a = ::RegisterClassEx( &wc );

        g_wndClassInitialized = true;
    }

    m_hWnd2 = ::CreateWindowEx( WS_EX_APPWINDOW, L"SkanTwainMessageWindowClass", L"SkanTwainMessageWindow", 0, 0, 0, 0, 0, NULL /*HWND_MESSAGE*/, NULL, NULL, NULL );

    if ( !( m_dsmConnected = connectDSM() ) ) return 0;

    ::SetEvent( m_hEvent );

    MSG msg = { 0 };

    while ( ::GetMessage( ( LPMSG ) &msg, NULL, 0, 0 ) )
    {
        bool isTMSG = false;

        switch ( msg.message )
        {
        case MSG_DEVICEEVENT:
            {
                TW_DEVICEEVENT de = { 0 };

                TW_UINT16 twrc = dsmEntry( DG_CONTROL, DAT_DEVICEEVENT, MSG_GET, ( TW_MEMREF ) &de );

                if ( twrc == TWRC_SUCCESS )
                {
                    int h = 0;
                }
            }
            break;

        case TMSG_SCAN:
            {
                m_scanResult = ( int ) doScan();
                ::SetEvent( m_hEvent );
                isTMSG = true;
            }
            break;

        case TMSG_CONNECT:
            //m_scanResult = doConnect();
            isTMSG = true;
            break;

        case TMSG_DISCONNECT:
            //m_scanResult = doDisconnect();
            isTMSG = true;
            break;
        }

        if ( isTMSG ) continue;

        ::TranslateMessage( &msg );
        ::DispatchMessage( &msg );
    }

    return 0;
}

void TwainModel::setPaperSource( PaperSource value )
{
    if ( ( value == PaperSource::Flatbed && this->m_isFlatbedSupported ) || ( value == PaperSource::Feeder && this->m_isFeederSupported ) )
    {
        m_paperSource = value;
    }
}

void TwainModel::deviceChange( bool state )
{
    m_messageListener->onDeviceConnection( state );
}
